﻿/*
COPYRIGHT (C) 2011-2013 Raynald Messié

THIS FILE IS PART OF SCRUMPILOT.

SCRUMPILOT IS FREE SOFTWARE: YOU CAN REDISTRIBUTE IT AND/OR MODIFY IT UNDER THE TERMS OF 
THE GNU LESSER GENERAL PUBLIC LICENSE VERSION v2.1 AS PUBLISHED BY THE FREE SOFTWARE FOUNDATION.

SCRUMPILOT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY; WITHOUT
EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU LESSER
GENERAL PUBLIC LICENSE FOR MORE DETAILS.

YOU SHOULD HAVE RECEIVED A COPY OF THE GNU LESSER GENERAL PUBLIC LICENSE ALONG WITH SCRUMPILOT. 
IF NOT, SEE <HTTP://WWW.GNU.ORG/LICENSES/>.
*/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using ScrumPilot.Domain.Models;
using ScrumPilot.Helpers;
using ScrumPilot.TFSHelpers;

namespace ScrumPilot.Domain.BPL
{
    public class DashBoardServices : BusinessProcessLayer
    {
        protected const string RelaseBugsListName = "currentrelease"; // set this const in db/params
        protected List<Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem> BugItemsList;
        protected readonly WorkItemType[] NoUserStories = { WorkItemType.UserStory, WorkItemType.Defect };

        public DashBoardServices(TFSServices tfs) : base(tfs) { }
        public DashBoardServices(TFSServices tfs, String tfsRootUrl) : base(tfs, tfsRootUrl) { }

        protected scrumpilotparam ScrumPilotParam
        {
            get
            {
                using (var unitOfWork = new UnitOfWork())
                {
                    return unitOfWork.ScrumPilotParamRepository.GetOne() ?? (new scrumpilotparam().GetDefault());
                }
            }
        }

        protected void GetAllBugItems()
        {
            var qry = new TFSQuery(TFS.TfsWorkItemStore);
            // Get all workitems using in dashboard
            BugItemsList = qry.GetWorkItemList(this.FollowedProjectsDictonary,
                new List<string> { "Bug" },
                new List<WorkItemState> { WorkItemState.InProgress, WorkItemState.Todo, WorkItemState.Reopen });
        }

        protected KeyDataIndicators SetKeyindicatorCSS(KeyDataIndicators keys)
        {
            if (keys.BugsCount >= ScrumPilotParam.buglevelred)
            {
                keys.CSSBugs = "keyred";
            }
            else if (keys.BugsCount >= ScrumPilotParam.buglevelorange)
            {
                keys.CSSBugs = "keyorange";
            }
            if (keys.CriticalItemsCount > 0)
            {
                keys.CSSCriticalItems = "keyred";
            }
            return keys;
        }

        protected List<BugsItem> GetBugsList()
        {
            var lstRet = new List<BugsItem>();
            if (this.BugItemsList != null)
            {
                var iterations = this.BugItemsList.GroupBy(g => g.IterationPath).Select(s => s.Key).OrderBy(o => o).ToList();
                lstRet.AddRange(from iteration in iterations
                                let item = this.BugItemsList.First(p => p.IterationPath == iteration)
                                select new BugsItem
                                {
                                    ID = item.Project.Id,
                                    Name = iteration,
                                    ProjectID = item.Project.Id,
                                    ProjectName = item.Project.Name,
                                    Totals = this.BugItemsList.Count(p => p.IterationPath == iteration),
                                    Actives = this.BugItemsList.Count(p => p.IterationPath == iteration && !String.IsNullOrWhiteSpace(p.GetAssignedTo()))
                                });
                lstRet = lstRet.OrderByDescending(p => p.Totals).ThenByDescending(p => p.Actives).ToList();
            }
            return lstRet;
        }
    }

    public class ScrumDashBoardServices : DashBoardServices
    {
        private List<TFSWallContentObjectWithUser> _pendingChanges;

        public ScrumDashBoardServices(TFSServices tfs) : base(tfs) { }

        #region Services
        public KeyPoints GetKeyPoints(Int32 currentUserID, CultureInfo ci)
        {
            CurrentCulture = ci;
            CurrentUserID = currentUserID;
            var keyPointsRet = new KeyPoints();

            // force to read in DB
            if (this.CurrentSprintsDictonary != null && this.FollowedProjectsDictonary != null)
            {
                this.GetMainWorkItems();

                keyPointsRet = new KeyPoints
                {
                    Projects = this.GetKeyPointProjects(),
                    WhoDoesWhat = GetWhoDoesWhat(),
                    KeyIndicators = new KeyDataIndicators
                    {
                        BugsCount = BugItemsList == null ? 0 : BugItemsList.Count(),
                        UserstoriesCountDown = this.WorkItemsList == null ? 0 :
                               this.WorkItemsList.AsParallel().WithDegreeOfParallelism(2).Count(
                                   p => p.GetWorkItemType() == WorkItemType.UserStory && p.IsActive()),
                        CriticalItemsCount = (this.WorkItemsList == null ? 0 :
                                this.WorkItemsList.Count(
                                p => p.GetWorkItemType() == WorkItemType.Task && p.IsActive() && p.GetPriority() == 1)) + // Count critical tasks
                                (this.BugItemsList == null ? 0 : this.BugItemsList
                                .Count(p => p.IsActive() && p.GetPriority() == 1)) // count critical bug
                        // in our definition an Item/Task in priority 1 is critical for get a good sprint
                    },
                    Bugslist = this.GetBugsList(),
                    SprintGap =
                        String.Format("from {0:d} to {1:d} ({2}/{3} workdays)",
                                      CurrentSprint.StartDate, CurrentSprint.EndDate,
                                      CurrentSprint.WorkingDays - CurrentSprint.RemainDays,
                                      CurrentSprint.WorkingDays),
                    SprintName = CurrentSprint.Name,
                    // Set the kanban value
                    KanbanIpMax = this.ScrumPilotParam.kanbanmaxdoing,
                    KanbanTtMax = this.ScrumPilotParam.kanbanmaxtotest
                };

                // Sort bugslist in two categories : release and not release
                keyPointsRet.ReleaseBugslist =
                    keyPointsRet.Bugslist.Where(p => p.Name.ToLower().Contains(RelaseBugsListName)).ToList();
                keyPointsRet.Bugslist =
                    keyPointsRet.Bugslist.Where(p => !p.Name.ToLower().Contains(RelaseBugsListName)).ToList();


                // Set the CSS for keys
                keyPointsRet.KeyIndicators = this.SetKeyindicatorCSS(keyPointsRet.KeyIndicators);
                keyPointsRet.KeyIndicators.TotalScore = keyPointsRet.WhoDoesWhat.Sum(p => p.PendingScore);

                // Set the BurnDown graph data
                this.GetBurnDownData(ref keyPointsRet);

            }
            return keyPointsRet;
        }
        #endregion

        #region Helpers
        private void GetMainWorkItems()
        {
            if (this.WorkItemsList == null && this.BugItemsList == null)
            {
                var po = new ParallelOptions
                {
                    MaxDegreeOfParallelism = 2
                };
                Parallel.Invoke(po, GetAllSprintWorkItems, GetAllBugItems);
                _pendingChanges = TFS.GetAllPendingChange();
            }
        }

        public void GetAllSprintWorkItems()
        {
            if (TFS != null && TFS.IsAuthenticated && TFS.TfsWorkItemStore != null)
            {
                TFS.RefreshCache();
                var qry = new TFSQuery(TFS.TfsWorkItemStore);
                // Get all workitems using in dashboard
                this.WorkItemsList = qry.GetWorkItemList(this.CurrentSprintsDictonary, null, null, false, false);
            }
        }

        private List<WorkItemProject> GetKeyPointProjects()
        {
            var projects = this.CurrentSprintsProjectIDDictonary();
            var lstRet = new List<WorkItemProject>(projects.Count);
            var spt = this.CurrentSprint;

            if (this.WorkItemsList != null)
            {
                foreach (var project in projects)
                {
                    var witl = WorkItemsList.Where(p => p.Project.Id == project.Key).ToList();
                    var uswitl = witl.Where(p => p.GetWorkItemType() == WorkItemType.UserStory).ToList();
                    var wwitl = witl.Where(workItem => workItem.GetWorkItemType() == WorkItemType.Task).ToList();
                    // witl.Where(workItem => _workItems.Contains(workItem.GetWorkItemType())).ToList();

                    var item = new WorkItemProject
                    {
                        ID = project.Key,
                        Name = project.Value.IndexOf('\\') > 0 ?
                             project.Value.Substring(0, project.Value.IndexOf('\\')) :
                             project.Value,
                        CurrentSprint = project.Value,
                        TotalScore = wwitl.AsParallel().WithDegreeOfParallelism(2).Sum(workItem => workItem.GetScrumPilotScore()),
                        TotalCompletedScore = wwitl.AsParallel().WithDegreeOfParallelism(2).Where(p => p.GetState() == WorkItemState.Done).Sum(workItem => workItem.GetScrumPilotScore()),
                        /*   CompletedTime = wwitl.AsParallel().WithDegreeOfParallelism(2).Sum(workItem => workItem.GetCompletedWork()),
                           EstimatedTime = wwitl.AsParallel().WithDegreeOfParallelism(2).Sum(workItem => workItem.GetOriginalEstimate()),
                           RemainingTime = wwitl.AsParallel().WithDegreeOfParallelism(2).Sum(workItem => workItem.GetRemainingWork()),*/
                        WorkItemClasses = new List<int>(3)
                                                            {
                                                               witl.AsParallel().WithDegreeOfParallelism(2).Count(p=>!NoUserStories.Contains(p.GetWorkItemType()) && p.GetState()== WorkItemState.Done),
                                                               witl.AsParallel().WithDegreeOfParallelism(2).Count(p=>!NoUserStories.Contains(p.GetWorkItemType()) ),
                                                               0
                                                            },
                        UserStories = new List<int>(3)
                                                            {
                                                                uswitl.AsParallel().WithDegreeOfParallelism(2).Count(p=>p.GetState()==WorkItemState.Todo),
                                                                uswitl.AsParallel().WithDegreeOfParallelism(2).Count(p=>p.GetState()==WorkItemState.InProgress && !p.IsUserStoryReadyToTest()),
                                                                uswitl.AsParallel().WithDegreeOfParallelism(2).Count(p=>p.IsUserStoryReadyToTest()),
                                                                uswitl.AsParallel().WithDegreeOfParallelism(2).Count(p=>p.GetState()== WorkItemState.Done),
                                                               0,
                                                               0,
                                                               0
                                                            },
                        Workers = witl.Where(p => !String.IsNullOrWhiteSpace(p.GetAssignedTo())
                                                  && p.GetState() == WorkItemState.InProgress
                                                  && p.GetWorkItemType() == WorkItemType.Task)
                                                  .GroupBy(p => p.GetAssignedTo()).Count()
                    };

                    item.CurrentSprint = item.CurrentSprint.Substring(item.Name.Length + 1);

                    int workDays = spt.WorkingDays - spt.RemainDays;

                    // ReSharper disable PossibleLossOfFraction
                    decimal avgWorkItemByDay = item.WorkItemClasses[1] / spt.WorkingDays;
                    // ReSharper restore PossibleLossOfFraction

                    // if workitems done < (average work item by day * worked day) => color orange
                    // if woritems done = total work item => color green
                    item.WorkItemClasses[2] = item.WorkItemClasses[0] < avgWorkItemByDay * workDays ?
                        1 : item.WorkItemClasses[0] == item.WorkItemClasses[1] ? 2 : 0;

                    var usinSprint = uswitl.Count();
                    // ReSharper disable PossibleLossOfFraction
                    avgWorkItemByDay = usinSprint / spt.WorkingDays;
                    // ReSharper restore PossibleLossOfFraction

                    item.UserStories[4] = item.UserStories[3] < avgWorkItemByDay * workDays ?
                     1 : item.UserStories[3] == usinSprint ? 2 : 0;


                    // ReSharper disable PossibleLossOfFraction
                    decimal avgScoreByDay = item.TotalScore / spt.WorkingDays;
                    // ReSharper restore PossibleLossOfFraction
                    item.TimeRatioColor = item.TotalScore - item.TotalCompletedScore < 0 ? 3 :
                        item.TotalCompletedScore < avgScoreByDay * workDays ? 2 :
                        item.UserStories[4] == 2 ? 1 : 0;

                    lstRet.Add(item);
                }

                int sumUsInProgress = lstRet.Sum(itemProject => itemProject.UserStories[1]);
                int sumUsToDo = lstRet.Sum(itemProject => itemProject.UserStories[2]);

                foreach (var itemProject in lstRet)
                {
                    itemProject.UserStories[5] = ScrumPilotParam.kanbanmaxdoing > 0 && sumUsInProgress > ScrumPilotParam.kanbanmaxdoing ? 1 : 0;
                    itemProject.UserStories[6] = ScrumPilotParam.kanbanmaxtotest > 0 && sumUsToDo > ScrumPilotParam.kanbanmaxtotest ? 1 : 0;
                }

                lstRet = lstRet.OrderBy(p => p.Name).ToList();
            }
            return lstRet;
        }

        public WorkItemProject GetKeyPointProject(int id)
        {
            using (var unitOfWork = new UnitOfWork())
            {
                var project = unitOfWork.ProjectRepository.GetById(id);
                var wiList = TFS.GetWIList(project.name, project.currentsprint, false);

                var wiPret = new WorkItemProject
                                 {
                                     ID = project.projectid,
                                     Name = project.name,
                                     CurrentSprint = project.currentsprint,
                                     WorkItemClasses = new List<int>(2)
                                                          {
                                                              wiList.Count(
                                                                  p => p.GetWorkItemType() == WorkItemType.Task &&
                                                                       p.GetState() == WorkItemState.Done),
                                                              wiList.Count(p => p.GetWorkItemType() != WorkItemType.Task)
                                                          },
                                     UserStories = new List<int>(2)
                                                      {
                                                          wiList.Count(
                                                              p => p.GetWorkItemType() == WorkItemType.UserStory &&
                                                                   p.GetState() == WorkItemState.Done),
                                                          wiList.Count(
                                                              p => p.GetWorkItemType() == WorkItemType.UserStory),
                                                      }

                                 };
                return wiPret;
            }
        }

        private List<WhoDoesWhatUser> GetWhoDoesWhat()
        {
            using (var unitOfWork = new UnitOfWork())
            {
                var usrs =
                    ((FollowedUsers != null && FollowedUsers.Count > 0)
                         ? unitOfWork.UserRepositery.Filter(p => FollowedUsers.Contains(p.userid))
                         : unitOfWork.UserRepositery.All()).ToList();
                var lstRet = new List<WhoDoesWhatUser>(usrs.Count);
                var qry = new TFSQuery(TFS.TfsWorkItemStore);
                // DateTime dStart = DateTimeHelpers.ScrumPilotStartPeriod(this.TFSParams.ScrumPilotScorePeriod, ci);
                var myProjects = (Dictionary<string, string>)this.FollowedProjectsDictonary;

                foreach (user usr in usrs)
                {
                    DateTime? dMax = this.LastDate(usr.userid);
                    qry.GetScoredUserWorkItemList(usr.name, this.CurrentSprint.StartDate, myProjects);
                    lstRet.Add(new WhoDoesWhatUser
                                   {
                                       ID = usr.userid,
                                       Name = usr.name,
                                       Mail = usr.mail,
                                       PendingScore = qry.GetUserScore(),
                                       PendingChangeCount = CountPendingChanges(usr.tfsname, ref dMax),
                                       PendingWorkItems = CountPendingWorkItems(usr.name, ref dMax, qry),
                                       CompletedHours = (int)qry.GetUserTimeCompleted(),
                                       Date = dMax
                                   });
                }
                return
                    lstRet.OrderBy(p => p.Name)
                          .ThenByDescending(p => p.PendingChangeCount)
                          .OrderByDescending(p => p.PendingScore)
                          .ToList();
            }
        }


        private Nullable<DateTime> LastDate(int usrId)
        {
            using (var unitOfWork = new UnitOfWork())
            {
                if (!unitOfWork.EventRepository.Filter(p => p.userid == usrId).Any()) return null;
                return unitOfWork.EventRepository.Filter(p => p.userid == usrId).Max(p => p.createdate);
            }
        }

        private int CountPendingChanges(String tfsName, ref Nullable<DateTime> maxDate)
        {
            var lst = _pendingChanges.Where(p => p.Username == tfsName).ToArray();
            maxDate = lst.Any() ? DateTimeHelpers.Max(lst.Max(p => p.Date), maxDate) : maxDate;
            return lst.Count();
        }

        private int CountPendingWorkItems(String tfsid, ref Nullable<DateTime> maxDate, TFSQuery qry)
        {
            var lst = qry.GetWorkItemListForUser(this.FollowedProjectName, new List<WorkItemState> { WorkItemState.Todo, WorkItemState.InProgress, WorkItemState.Reopen },
                new List<string> { "Task", "Bug" }, tfsid);
            maxDate = lst.Any() ? DateTimeHelpers.Max(lst.Max(p => p.ChangedDate), maxDate) : maxDate;
            return lst.Count();
        }

        private void GetBurnDownData(ref KeyPoints keys)
        {
            var wwitl = this.WorkItemsList.AsParallel().WithDegreeOfParallelism(2).Where(workItem => workItem.GetWorkItemType() == WorkItemType.Task).ToList();
            var lstDate = DateTimeHelpers.GetDateWorkDays(this.CurrentSprint.StartDate, this.CurrentSprint.EndDate);
            var scores = new List<Nullable<int>>(this.CurrentSprint.WorkingDays + 1);
            var score = keys.KeyIndicators.TotalScore = keys.Projects.Sum(p => p.TotalScore);

            int i = this.CurrentSprint.WorkingDays + 1;
            lstDate.Insert(0, lstDate[0].AddDays(-1));

            foreach (var dStart in lstDate)
            {
                if (i >= this.CurrentSprint.RemainDays)
                {
                    DateTime start = dStart;
                    score -= wwitl.AsParallel().WithDegreeOfParallelism(2).Where(p =>
                                                                                     {
                                                                                         var dateTaskCompleted = p.GetDateTaskCompleted();
                                                                                         return dateTaskCompleted != null && (p.GetState() == WorkItemState.Done
                                                                                                                               &&
                                                                                                                               dateTaskCompleted.Value.Date == start.Date);
                                                                                     })
                        .Sum(workItem => workItem.GetScrumPilotScore());
                    scores.Add(score);
                }
                else
                {
                    scores.Add(null);
                }
                i--;
            }

            keys.BurnDown = new BurndownGraph
            {
                IdealBurnDown = new[] { keys.KeyIndicators.TotalScore, 0 },
                RealBurnDown = scores.ToArray(),
                XAxis = new[] { this.CurrentSprint.WorkingDays - 1, (this.CurrentSprint.WorkingDays - this.CurrentSprint.RemainDays) + 1 },
                YAxis = new[] { 0, keys.KeyIndicators.TotalScore / 2, keys.KeyIndicators.TotalScore }
            };
        }
        #endregion

    }
}
